Skip to content

Let markdown tables and code blocks scroll sideways in mobile messages - #2283

Merged
SawyerHood merged 1 commit into
mainfrom
bb/fix-mobile-table-horizontal-scrolling-thr_4hxgryxmas
Aug 22, 2026
Merged

Let markdown tables and code blocks scroll sideways in mobile messages#2283
SawyerHood merged 1 commit into
mainfrom
bb/fix-mobile-table-horizontal-scrolling-thr_4hxgryxmas

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

What was wrong

Markdown tables in mobile timeline messages did not scroll sideways, and fenced code blocks never scrolled sideways anywhere. On the new architecture, RCTScrollViewComponentView._shouldDisableScrollInteraction walks a ScrollView's ancestors and, if one of them is the JS responder, touchesShouldCancelInContentView: returns NO, so the UIScrollView never takes over the touch. A Pressable claims the JS responder as soon as a touch starts. Timeline messages wrap their markdown in long-press Pressables (message actions, quote-this-block), so every table under them was blocked. CodeBlock wrapped its own ScrollView in a copy-on-long-press Pressable, so it was blocked even in the dev showcase.

What changed

  • apps/mobile/src/markdown/MarkdownTable.tsx — the long-press target is a Pressable inside the horizontal ScrollView. A descendant responder claims the touch first, and the ScrollView cancels it normally once the drag starts. Takes an onLongPress prop.
  • apps/mobile/src/markdown/render-blocks.tsxMarkdownBlock branches on node.type === "table": plain View wrapper, handler passed directly. The handler is the block's quote shortcut, or the new body-level fallback when there is none. The comment notes that code blocks copy instead of quoting.
  • apps/mobile/src/markdown/MarkdownContext.tsx, Markdown.tsx — new onLongPress (body-level fallback). AssistantMessageRow, AuthoredUserMessage, and GeneratedMessageRow pass their message-actions handler, so tables in user and generated messages keep their long-press.
  • apps/mobile/src/markdown/CodeBlock.tsx — inner Pressable around the body, with the body padding on it so a drag that starts in a gutter scrolls too. The outer Pressable stays for the header.
  • apps/mobile/src/ui/long-press.tsLONG_PRESS_DELAY_MS (350) shared by all seven delayLongPress sites and both inner Pressables, so nested targets never race each other.
  • apps/mobile/src/screens/dev/work-row-fixtures.ts — the closing assistant fixture in the work-rows showcase carries a table wider than the screen.
  • apps/mobile/e2e/flows/phase4a-work-rows.yaml — swipes that table, asserts the hidden column appears, long-presses a cell, and asserts "Copy text" → "Copied".

No wire, CLI, or doc changes.

Not in this PR: moving message-level long-press to react-native-gesture-handler (which would remove the need for inner Pressables altogether) is a larger design change for a follow-up. On Android, the handler-less inner Pressable on surfaces that leave selectable at its default (file previews, skill details) may cancel native text-selection long-press on table cells; not confirmed on a device.

How you verified

  • Reproduced on an iPhone 17 Pro simulator with Maestro before the fix: the same table scrolled in the plain markdown showcase and did not scroll under AssistantMessageRow; a bare Pressable around a table reproduced it in isolation. Instrumented frame (368pt) vs. content (858pt) widths confirmed the ScrollView had room to scroll and received no scroll events.
  • After the fix, on the simulator: the timeline table scrolls and reveals the hidden column; long-press on a cell opens the message actions and "Copy text" copies; long-press on a table inside a user-message bubble opens the actions (temporary fixture, reverted); the TS code block scrolls.
  • The new phase4a-work-rows.yaml steps fail before the fix (the hidden column never appears) and pass after.
  • pnpm exec turbo run lint typecheck test --filter=@bb/mobile — all tasks pass; the 19 lint warnings are pre-existing in untouched files.

AGENT GENERATED

On the new architecture a ScrollView refuses to take over a touch while
one of its ancestors is the JS responder, and a Pressable claims the
responder as soon as a touch starts. Timeline messages wrap their markdown
in long-press Pressables, so tables under them never scrolled sideways;
CodeBlock wrapped its own ScrollView in a copy Pressable and never scrolled
anywhere.

Move each long-press target inside its ScrollView (a descendant responder
is cancelled normally once the drag starts). Tables take the block's
quote handler, or the body-level message handler as a fallback, so user
and generated messages keep their long-press on tables. Code bodies carry
the body padding on the inner Pressable so gutter drags scroll too. Share
one LONG_PRESS_DELAY_MS across nested targets.

The work-rows showcase ends with a wide table; the phase4a-work-rows e2e
flow swipes it, asserts the hidden column appears, and long-presses it.

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood
SawyerHood merged commit fff3ae8 into main Aug 22, 2026
15 checks passed
@SawyerHood
SawyerHood deleted the bb/fix-mobile-table-horizontal-scrolling-thr_4hxgryxmas branch August 22, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant